﻿using FastData.Caching;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;

namespace FastData
{
    public enum SortDirection
    {
        NotSet, Asc, Desc
    }

    public class SelectQuery : QueryBase
    {
        internal SelectQuery(DataAccess data)
            : base(data)
        {
        }

        public SelectQuery From(string tableName)
        {
            TableName = tableName;
            return this;
        }

        public SelectQuery InnerJoin(string tableName, string left, string right)
        {
            return Join(JoinType.Inner, tableName, left, right);
        }

        public SelectQuery InnerJoin(string tableName, string where)
        {
            return Join(JoinType.Inner, tableName, where);
        }

        public SelectQuery LeftJoin(string tableName, string left, string right)
        {
            return Join(JoinType.Left, tableName, left, right);
        }

        public SelectQuery LeftJoin(string tableName, string where)
        {
            return Join(JoinType.Left, tableName, where);
        }

        public SelectQuery RightJoin(string tableName, string left, string right)
        {
            return Join(JoinType.Right, tableName, left, right);
        }

        public SelectQuery RightJoin(string tableName, string where)
        {
            return Join(JoinType.Right, tableName, where);
        }

        private SelectQuery Join(JoinType type, string tableName, string left, string right)
        {
            return Join(type, tableName, Data.Provider.GetDbObjectName(left) + "=" + Data.Provider.GetDbObjectName(right));
        }

        private SelectQuery Join(JoinType type, string tableName, string where)
        {
            Joins.Add(new JoinInfo()
            {
                Type = type,
                TableName = tableName,
                Where = where
            });
            return this;
        }

        public SelectQuery Distinct()
        {
            IsDistinct = true;
            return this;
        }


        internal SelectQuery Fields(params string[] fields)
        {
            FieldNames = string.Join(",", fields.Select(f => Data.Provider.GetDbObjectName(f)));
            return this;
        }

        public SelectQuery OrderBy(string field, SortDirection sortDirection = SortDirection.NotSet)
        {
            SetOrderBy(field, sortDirection);
            return this;
        }

        public SelectQuery Take(int count)
        {
            TakeCount = count;
            return this;
        }

        public SelectQuery Skip(int count)
        {
            SkipCount = count;
            return this;
        }

        public SelectQuery Page(int pageNo, int pageSize)
        {
            Take(pageSize);
            Skip((pageNo - 1) * pageSize);
            return this;
        }

        public SelectQuery Where(string whereText)
        {
            base.SetWhere(whereText);
            return this;
        }

        public SelectQuery Where(string fieldName, object value)
        {
            base.SetWhere(fieldName, value);
            return this;
        }

        public SelectQuery Where(FilterField[] filters)
        {
            base.SetWhere(filters);
            return this;
        }

        public SelectQuery Where(string fieldName, FilterType filterType, object value)
        {
            base.SetWhere(fieldName, filterType, value);
            return this;
        }

        public SelectQuery Param(string name, object value)
        {
            base.AddParameter(name, value);
            return this;
        }

        public SelectQuery Param(DbParameter parameter)
        {
            base.AddParameter(parameter);
            return this;
        }

        public SelectQuery CacheDependencyOnTableUsage()
        {
            var dt = Data.Provider.GetViewTableUsage(Data, TableName).Cache(TimeSpan.FromDays(1)).ExecuteDataTable();
            foreach (DataRow row in dt.Rows)
            {
                CacheDependency((string)row[0]);
            }
            return this;
        }

        public SelectQuery Cache(TimeSpan slidingExpiration)
        {
            SlidingExpiration = slidingExpiration;
            return this;
        }

        public SelectQuery Cache(DateTime absoluteExpiration)
        {
            AbsoluteExpiration = absoluteExpiration;
            return this;
        }

        public SelectQuery CacheDependency(TableDependency tableDependency)
        {
            CacheDependencies.Add(tableDependency);
            return this;
        }

        public SelectQuery CacheDependency(string tableName)
        {
            return CacheDependency(new TableDependency(tableName));
        }

        public object ExecuteScalar()
        {
            return Data.ExecuteScalar(this);
        }

        public async Task<object> ExecuteScalarAsync()
        {
            return await Data.ExecuteScalarAsync(this);
        }

        public T ExecuteScalar<T>()
        {
            return (T)ExecuteScalar();
        }

        public async Task<DbDataReader> ExecuteReaderAsync()
        {
            return await Data.ExecuteReaderAsync(this);
        }

        public DbDataReader ExecuteReader()
        {
            return Data.ExecuteReader(this);
        }

        public void ExecuteLazy()
        {
            Data.ExecuteLazy(this);
        }

        public async Task<DataTable> ExecuteDataTableAsync()
        {
            return await Data.ExecuteDataTableAsync(this);
        }

        public DataTable ExecuteDataTable()
        {
            return Data.ExecuteDataTable(this);
        }

        public async Task<IEnumerable<T>> ExecuteAsync<T>()
        {
            return await Data.ExecuteAsync<T>(this);
        }

        public IEnumerable<T> Execute<T>()
        {
            return Data.Execute<T>(this);
        }

        public DataRow ExecuteDataRow()
        {
            return Data.GetDataRow(ExecuteDataTable());
        }

        internal override string GetCommandText(ExecuteType type)
        {
            return Data.Provider.CreateSelectText(this, type);
        }
    }

}
